import { getFreeSpace, isNearEdge } from '@/utils/position'
import { SquadTask } from '../types'
import { goTo } from '@/modules/move'
import { creepPosBipartiteMatch } from '@/algorithm/bipartiteMatch'
import { RoomCallBack, isMyRoom } from '@/ayaka'
import { getCreepWarAbility, isEnemy } from '@/utils/war'
import { squadMove } from './move'

/**
 * 四人小队工作
 */
export const squadWork = (creeps: Creep[], task: SquadTask) => {
    creeps = creeps.filter((creep) => !creep.spawning)
    if (!creeps.length) return

    creeps.forEach((creep) => {
        globalAttack(creep.room)
        globalHeal(creep.room)
    })

    if (!task.spawnComplete || creeps.some((creep) => creep.spawning)) {
        creeps.forEach((creep) => {
            if (creep.room.name !== creep.memory.belong || isNearEdge(creep.pos, 3)) {
                goTo(creep, new RoomPosition(25, 25, creep.memory.belong), { range: 20 })
                return
            }

            if (isNearEdge(creep.pos, 5) || Game.time % 4) {
                return
            }

            goTo(creep, new RoomPosition(25, 25, task.roomName), { range: 23 })
            return
        })
        return
    }

    task.formation = task.formation || 'line'
    const standPos = getStandPos(creeps)
    if (!standPos) return

    // 靠近任务房间并且有足够位置时，开始组成正方队形
    if (task.formation === 'line' && Game.map.getRoomLinearDistance(creeps[0].room.name, task.roomName) <= 1) {
        if (!squareFormation(creeps, standPos)) {
            task.formation = 'square'
            return
        }
    }

    // 四人小队移动
    if (standPos.roomName !== task.roomName) {
        const targetPos = new RoomPosition(25, 25, task.roomName)
        if (squareFormation(creeps, standPos)) {
            squadMove(creeps, task, standPos, targetPos, 20)
        }
    } else {
        const targetRoom = Game.rooms[task.roomName]
        const target = standPos.findClosestByRange([
            ...targetRoom.enemys,
            ...targetRoom.enemyStructures.filter(
                (s) => !['container', 'road', 'constructedWall'].includes(s.structureType),
            ),
        ])
        if (!target) return
        if (target.pos.getRangeTo(standPos) <= 4) {
            if (attackFormationChange(creeps, target.pos)) return
        }
        if (squareFormation(creeps, standPos)) {
            squadMove(creeps, task, standPos, target.pos, 2)
        }
    }
}

/**
 * 获取四人小队左上角队员的位置
 */
function getStandPos(creeps: Creep[]): RoomPosition | null {
    // team的所有爬中 x+y 坐标最小的就是左上角队员的位置
    let sum = Infinity
    let standPos = null
    for (const { pos } of creeps) {
        if (sum > pos.x + pos.y) {
            sum = pos.x + pos.y
            standPos = pos
        }
    }
    return standPos
}

/**
 * 组成正方队形，返回 true 表示不需要调整队形，返回 false 表示调整了队形
 */
function squareFormation(creeps: Creep[], standPos: RoomPosition) {
    // 如果左上角爬在房间边缘，就不调整队形
    if (standPos.x >= 48 || standPos.y >= 48 || standPos.x < 2 || standPos.y < 2) {
        return true
    }

    // 以左上角爬为基准组队
    const { x, y } = standPos
    const freePosSet = new Set<`${string}/${string}`>()
    getFreeSpace(standPos).forEach((pos) => freePosSet.add(`${pos.x}/${pos.y}`))
    if (!freePosSet.has(`${x + 1}/${y}`) || !freePosSet.has(`${x}/${y + 1}`) || !freePosSet.has(`${x + 1}/${y + 1}`)) {
        return true
    }

    const dpos = [
        [0, 0],
        [1, 0],
        [0, 1],
        [1, 1],
    ]
    const dposSet = new Set(dpos.map((p) => `${p[0]}/${p[1]}`))

    let isNeedAdjust = false
    for (const creep of creeps) {
        if (!dposSet.has(`${creep.pos.x - x}/${creep.pos.y - y}`)) {
            isNeedAdjust = true
            break
        }
    }
    if (!isNeedAdjust) return true

    const squarePos = dpos.map(([dx, dy]) => new RoomPosition(x + dx, y + dy, standPos.roomName))
    const creepPosMap = creepPosBipartiteMatch(creeps, squarePos, 20)
    creeps.forEach((creep) => {
        const pos = creepPosMap[creep.name]
        if (pos && !creep.pos.isEqualTo(pos)) {
            goTo(creep, pos, { range: 0 })
        }
    })
    return false
}

/**
 * 攻击队形变换，使我方攻击力最大的两个爬朝向目标
 */
function attackFormationChange(creeps: Creep[], targetPos: RoomPosition) {
    // 找到最近的两个位置
    const closestPos = creeps
        .sort((a, b) => a.pos.getRangeTo(targetPos) - b.pos.getRangeTo(targetPos))
        .map((c) => c.pos)
        .slice(0, 2)

    // 找到攻击最大的两个爬
    const attackCreeps = creeps
        .filter((creep) => {
            getCreepWarAbility(creep)
            return creep._warAbility.attack > 0 || creep._warAbility.rangeAttack > 0
        })
        .sort(
            (a, b) =>
                b._warAbility.attack + b._warAbility.rangeAttack - (a._warAbility.attack + a._warAbility.rangeAttack),
        )
        .slice(0, 2)
        .filter((creep) => !closestPos.includes(creep.pos))

    // 每个位置对应的爬
    const creepPosMap = creeps.reduce((map, creep) => {
        map[`${creep.pos.x}/${creep.pos.y}`] = creep
        return map
    }, {} as { [pos: `${string}/${string}`]: Creep })

    // 交换位置
    if (!attackCreeps.length || !closestPos.length) return false

    attackCreeps.forEach((creep) => {
        const nextPos = closestPos.pop()
        if (!nextPos) return
        const nextCreep = creepPosMap[`${nextPos.x}/${nextPos.y}`]
        creep.move(creep.pos.getDirectionTo(nextCreep))
        nextCreep.move(nextCreep.pos.getDirectionTo(creep))
    })
    return true
}

/**
 * 全局攻击
 */
export function globalAttack(room: Room) {
    if (room._globalAttack) return
    room._globalAttack = true

    if (!room.enemys) {
        room.enemys = room.find(FIND_HOSTILE_CREEPS).filter((c) => isEnemy(c))
    }
    if (!room.structures) {
        room.structures = room.find(FIND_STRUCTURES)
    }
    if (!room.enemyStructures) {
        // 是我的房间或者是我预定的房间
        if (isMyRoom(room) || room.controller?.reservation?.username === global.ayaka.myName) {
            room.enemyStructures = room.structures.filter((s) => s.my !== undefined && !s.my && s.hits)
        } else {
            room.enemyStructures = room.structures.filter((s) => !s.my && s.hits)
        }
    }
    if (!room.myCreeps) {
        room.myCreeps = room.find(FIND_MY_CREEPS)
    }

    // 过滤掉在 rampart 中的爬
    let enemyCreeps = room.enemys.filter(
        (c) => !c.pos.lookFor(LOOK_STRUCTURES).some((s) => s.structureType === STRUCTURE_RAMPART),
    )

    enemyCreeps.forEach((c) => getCreepWarAbility(c))
    let myCreeps = room.myCreeps.filter((c) => {
        const { attack, rangeAttack, heal } = getCreepWarAbility(c)
        return (attack && !heal) || rangeAttack
    })

    while (myCreeps.length) {
        let maxDamage = -5000
        let maxDamageCreep: Creep | undefined
        // 计算敌方每个爬收到的最大伤害
        enemyCreeps.forEach((e) => {
            e._damage = 0
            e._attackQueue = []
            myCreeps.forEach((creep) => {
                const distance = creep.pos.getRangeTo(e.pos)
                const { attack, rangeAttack } = creep._warAbility
                if (attack && distance <= 1) {
                    e._damage += attack + rangeAttack
                    e._attackQueue.push(creep)
                } else if (rangeAttack && distance <= 3) {
                    e._damage += rangeAttack - distance
                    e._attackQueue.push(creep)
                }
            })
            // 优先打受到伤害高且血少，同时可能使其破防的爬
            if (e._damage - e.hits > maxDamage && (e._damage > e._warAbility.heal || isMyRoom(room))) {
                maxDamage = e._damage
                maxDamageCreep = e
            }
        })
        if (maxDamageCreep) {
            // 攻击
            maxDamageCreep!._attackQueue.forEach((creep) => {
                const { attack, rangeAttack } = creep._warAbility
                if (attack) {
                    creep.attack(maxDamageCreep!)
                }
                if (rangeAttack) {
                    if (creep.pos.getRangeTo(maxDamageCreep!.pos) <= 1) {
                        creep.rangedMassAttack()
                    } else {
                        creep.rangedAttack(maxDamageCreep!)
                    }
                }
            })
            // 过滤
            enemyCreeps = enemyCreeps.filter((e) => e != maxDamageCreep)
            myCreeps = myCreeps.filter((creep) => !maxDamageCreep!._attackQueue.includes(creep))
        } else {
            // 攻击最近的爬或者建筑
            myCreeps.forEach((creep) => {
                const closestTarget = creep.pos.findClosestByRange(room.enemyStructures || [])
                if (!closestTarget) return
                const { attack, rangeAttack } = creep._warAbility
                const distance = creep.pos.getRangeTo(closestTarget)
                if (attack && distance <= 1) {
                    creep.attack(closestTarget)
                }
                if (rangeAttack) {
                    if (
                        distance <= 1 &&
                        !['road', 'constructedWall', 'container'].includes(closestTarget.structureType)
                    ) {
                        creep.rangedMassAttack()
                    } else if (distance <= 3) {
                        creep.rangedAttack(closestTarget)
                    }
                }
            })
            myCreeps = []
        }
    }
}

/**
 * 全局奶
 */
export function globalHeal(room: Room) {
    if (room._globalHeal) return
    room._globalHeal = true

    if (!room.myCreeps) {
        room.myCreeps = room.find(FIND_MY_CREEPS)
    }

    const healerSets = new Set(
        // 计算我方爬治疗数据
        room.myCreeps.filter((c) => {
            getCreepWarAbility(c)
            return c._warAbility.heal > 0
        }),
    )
    const wounds = room.myCreeps.filter((c) => c.hitsMax - c.hits > 0)
    // 扣的血量从高到低排序
    wounds.sort((a, b) => b.hitsMax - b.hits - (a.hitsMax - a.hits))

    // 两次遍历确保所有奶奶完
    wounds.forEach((w) => {
        let needMilk = (w.hitsMax - w.hits) * 2
        const healers = [...healerSets]
        healers.forEach((h) => {
            if (needMilk <= 0) return
            if (h.pos.getRangeTo(w) <= 1) {
                h.heal(w)
                needMilk -= h._warAbility.heal
                healerSets.delete(h)
            } else if (h.pos.getRangeTo(w) <= 3) {
                h.rangedHeal(w)
                needMilk -= h._warAbility.heal / 3
                healerSets.delete(h)
            }
        })
    })

    let healers = [...healerSets]
    function getRangeTo(pos: RoomPosition, targetPos: Creep | null) {
        if (!targetPos) return 50
        return pos.getRangeTo(targetPos)
    }
    if (!healers.length) return

    if (!room.enemys) {
        room.enemys = room.find(FIND_HOSTILE_CREEPS).filter((c) => isEnemy(c))
    }

    // 找到攻击性的敌人
    const enemys = room.enemys.filter((e) => {
        getCreepWarAbility(e)
        return e._warAbility.attack || e._warAbility.rangeAttack
    })

    // 按距离敌人的远近排序
    const creeps = room.myCreeps.sort(
        (a, b) =>
            getRangeTo(a.pos, a.pos.findClosestByRange(enemys)) - getRangeTo(b.pos, b.pos.findClosestByRange(enemys)),
    )
    // 奶距离敌人最近的
    healers.forEach((h) => {
        let creep: Creep | undefined = undefined
        if (creeps.length) creep = creeps.shift()
        if (creep && h.pos.isNearTo(creep.pos)) {
            h.heal(creep)
        } else {
            h.heal(h)
        }
        healerSets.delete(h)
    })

    healers = [...healerSets]
    // 剩下的奶自己
    healers.forEach((h) => {
        h.heal(h)
    })
}

declare global {
    interface Room {
        _globalAttack?: boolean
        _globalHeal?: boolean
        enemyStructures: Structure[]
    }

    interface Creep {
        _damage: number
        _attackQueue: Creep[]
    }
}
